package mf.words.navigation;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Stack;

import mf.tools.Tuple;
import mf.tools.Command;

/** 
 * Data model for navigating through the text.
 */
public class WordModel {
	
	//debug mode displays more informations
	private boolean DEBUG_MODE = false;
	private boolean border = false;
	private String infoText = "&nbsp;";

	//arraylist for the rules
	private ArrayList<Tuple<Integer>> rules = new ArrayList<Tuple<Integer>>();
	
	//arraylist for left and right derivations
	private ArrayList<ArrayList<Integer>> L = new ArrayList<ArrayList<Integer>>();
	private ArrayList<ArrayList<Integer>> R = new ArrayList<ArrayList<Integer>>();

	//stack for traversing the tree
	private Stack<Command> T = new Stack<Command>();
	
	//starting variable of grammar
	private int S;

	/** Creates a new instance of an unpairer of file f. */
	public WordModel(File f, boolean d) throws IOException {
		DEBUG_MODE = d;

		//read grammar
		int varNum = readGrammar(f);

		// finally preprocess mappings L, L_i and H as well as R, R_i and W.
		preprocessMaps();

		// initialize stack
		S = varNum;
		T.push(new Command(S, H(S), Command.LEFT_DIRECTION));
	}

	/** Reads the content of the grammar file f and returns the starting variable. */
	private int readGrammar(File f) throws FileNotFoundException, IOException {
		FileInputStream fr = new FileInputStream(f);
		int blockLen;
		int[] iBuf;
		int nrOfRules = 0;
		int textLeft = -1;
		int varNum;
		
		//get number of rules
		for (int i = 3; i >= 0; i--) {
			int x = fr.read();
			nrOfRules += x * (int) Math.pow(256, i);
		}
		
		if(DEBUG_MODE)
			System.out.println("File has "+nrOfRules+" rules.");

		//set first variable number
		varNum = 255 + nrOfRules;

		//get block length from number of rules
		blockLen = 1 + (int) (Math.log10(256 + nrOfRules) / Math.log10(256));

		//read the rules
		for (int i = 0; i < nrOfRules; i++) {
			int[] left = new int[blockLen];
			int[] right = new int[blockLen];

			for(byte k=0; k<blockLen; k++)
				left[k] = fr.read();
			for(byte k=0; k<blockLen; k++)
				right[k] = fr.read();
			
			rules.add(new Tuple<Integer>(toNumber(left), toNumber(right)));
		}

		//read the rest of the file and grammarify it
		iBuf = new int[blockLen];

		//read first blockLen bytes
		for(byte k=0; k<blockLen; k++)
			iBuf[k] = fr.read();
		
		textLeft = toNumber(iBuf);

		//now the rest..
		while (true) {
			//read next bytes
			iBuf = new int[blockLen];
			for(byte k=0; k<blockLen; k++)
				iBuf[k] = fr.read();

			int textRight = toNumber(iBuf);

			if (textRight >= 0) {
				rules.add(new Tuple<Integer>(textLeft, textRight));
				varNum++;
				textLeft = varNum;
			} else {
				break;
			}
		}
		
		if(DEBUG_MODE)
			for(int i=0; i<rules.size(); i++) {
				int l = rules.get(i).left(), r = rules.get(i).right();
			
				System.out.println(
					"v_{"+(i+256)+"} -> " +
					(l < 256 ? (char) l : "v_{" + l + "}") + " " +
					(r < 256 ? (char) r : "v_{" + r + "}")
				);
			}

		
		return varNum;
	}
	
	/** Navigate to the next right leaf. */
	public void navigateRight() {
		//remove top stack
		Command top = T.pop();
		Integer X_n = top.variable(), i_n = top.length(), d_n = top.direction();
		
		//if last direction was left
		if(d_n == Command.LEFT_DIRECTION) {
			if(i_n > 0) {
				Integer X_v = L(X_n, i_n-1);
				
				T.push(new Command(X_n, i_n-1, Command.LEFT_DIRECTION));
				T.push(new Command(X_v, 0, Command.RIGHT_DIRECTION));
				
				Integer X_ns = R(X_v, 0);
				if(X_ns > 255)
					T.push(new Command(X_ns, H(X_ns), Command.LEFT_DIRECTION));
			} else {	//i == 0
				if(X_n == S) {
					T.push(new Command(S, 0, Command.RIGHT_DIRECTION));
					
					Integer X_2 = R(S, 0);
					if(X_2 > 255)
						T.push(new Command(X_2, H(X_2), Command.LEFT_DIRECTION));
				} else {	//X_n != S
					Command t_nm1 = T.pop();
					
					T.push(new Command(t_nm1.variable(), t_nm1.length()+1, Command.RIGHT_DIRECTION));
					
					Integer X_ns = R(t_nm1.variable(), t_nm1.length()+1);
					if(X_ns > 255) 
						T.push(new Command(X_ns, H(X_ns), Command.LEFT_DIRECTION));
				}
			}
		} else {	//	d == R
			if(X_n == S) {
				T.push(top);
				infoText = "End of text is reached.";
				border = true;
			} else {	//X_n != S
				Command t_nm1 = T.pop();
				Integer X_nm1 = t_nm1.variable(), i_nm1 = t_nm1.length();
				
				if(i_nm1 > 0) {
					T.push(new Command(X_nm1, i_nm1 - 1, Command.LEFT_DIRECTION));
					
					Integer X_v = L(X_nm1, i_nm1 - 1);
					T.push(new Command(X_v, 0, Command.RIGHT_DIRECTION));
				
					Integer X_ns = R(X_v, 0);
					if(X_ns > 255) 
						T.push(new Command(X_ns, H(X_ns), Command.LEFT_DIRECTION));
				} else {	//i_nm1 == 0
					Integer X_ns;
					
					if(X_nm1 == S) {
						T.push(new Command(S, 0, Command.RIGHT_DIRECTION));
						
						X_ns = R(S, 0);
					} else {	//X_nm1 != S
						Command t_nm2 = T.pop();
						T.push(new Command(t_nm2.variable(), t_nm2.length()+1, Command.RIGHT_DIRECTION));
						
						X_ns = R(t_nm2.variable(), t_nm2.length()+1);
					}
					
					if(X_ns > 255) 
						T.push(new Command(X_ns, H(X_ns), Command.LEFT_DIRECTION));
				}
			}
		}
	}
	
	/** Navigate to the next left leaf. */
	public void navigateLeft() {
		//remove top stack
		Command top = T.pop();
		Integer X_n = top.variable(), i_n = top.length(), d_n = top.direction();

		//if last direction was left
		if(d_n == Command.LEFT_DIRECTION) {
			if(X_n == S) {
				T.push(top);
				infoText = "Beginning of text is reached.";
				border = true;
			} else {	//X_n != S
				Command t_nm1 = T.pop();
				Integer X_nm1 = t_nm1.variable(), i_nm1 = t_nm1.length();
				
				if(i_nm1 > 0) {
					T.push(new Command(X_nm1, i_nm1 - 1, Command.RIGHT_DIRECTION));
					
					Integer X_v = R(X_nm1, i_nm1 - 1);
					T.push(new Command(X_v, 0, Command.LEFT_DIRECTION));
				
					Integer X_ns = L(X_v, 0);
					if(X_ns > 255) 
						T.push(new Command(X_ns, W(X_ns), Command.RIGHT_DIRECTION));
				} else {	//i_nm1 == 0
					Integer X_ns;
					
					if(X_nm1 == S) {
						T.push(new Command(S, 0, Command.LEFT_DIRECTION));
						
						X_ns = L(S, 0);
					} else {	//X_nm1 != S
						Command t_nm2 = T.pop();
						T.push(new Command(t_nm2.variable(), t_nm2.length()+1, Command.LEFT_DIRECTION));
						
						X_ns = L(t_nm2.variable(), t_nm2.length()+1);
					}
					
					if(X_ns > 255) 
						T.push(new Command(X_ns, W(X_ns), Command.RIGHT_DIRECTION));
				}
			}
		} else {	//d_n = R
			if(i_n > 0) {
				Integer X_v = R(X_n, i_n-1);
				
				T.push(new Command(X_n, i_n-1, Command.RIGHT_DIRECTION));
				T.push(new Command(X_v, 0, Command.LEFT_DIRECTION));
				
				Integer X_ns = L(X_v, 0);
				if(X_ns > 255)
					T.push(new Command(X_ns, W(X_ns), Command.RIGHT_DIRECTION));
			} else {	//i == 0
				if(X_n == S) {
					T.push(new Command(S, 0, Command.LEFT_DIRECTION));
					
					Integer X_2 = L(S, 0);
					if(X_2 > 255)
						T.push(new Command(X_2, W(X_2), Command.RIGHT_DIRECTION));
				} else {	//X_n != S
					Command t_nm1 = T.pop();
					
					T.push(new Command(t_nm1.variable(), t_nm1.length()+1, Command.LEFT_DIRECTION));
					
					Integer X_ns = L(t_nm1.variable(), t_nm1.length()+1);
					if(X_ns > 255) 
						T.push(new Command(X_ns, W(X_ns), Command.RIGHT_DIRECTION));
				}
			}
		}
	}
	
	/** Turns an array of chars into a number assuming the array is a number to base 256.	*/
	private int toNumber(int[] x) {
		int ret = 0;

		for (int i = 0; i < x.length; i++)
			ret += x[i] * Math.pow(256, x.length - i - 1);

		return ret;
	}
	
	/** Preprocess the mappings L, L_i and H as well as R, R_i and W. */
	private void preprocessMaps() {
		for (int i = 0; i < rules.size(); i++) {
			ArrayList<Integer> L_i = new ArrayList<Integer>();
			ArrayList<Integer> R_i = new ArrayList<Integer>();

			Integer ell = rules.get(i).left();
			Integer r = rules.get(i).right();
			L_i.add(ell);
			R_i.add(r);

			if (ell > 255)
				L_i.addAll(L(ell));
			if (r > 255)
				R_i.addAll(R(r));

			L.add(L_i);
			R.add(R_i);
		}
	}
	
	/** Access to the left derivations of non-terminal F. */
	private ArrayList<Integer> L(int F) {
		return L.get(F - 256);
	}
	
	/** Access to the i-th element of the left derivations of non-terminal F. */
	private Integer L(int F, int i) {
		return L(F).get(i);
	}

	/** Length of the left derivations of non-terminal F. */
	private Integer H(int F) {
		return L(F).size() - 1;
	}

	/** Access to the right derivations of non-terminal F. */
	private ArrayList<Integer> R(int F) {
		return R.get(F - 256);
	}

	/** Access to the i-th element of the right derivations of non-terminal F. */
	private Integer R(int F, int i) {
		return R(F).get(i);
	}

	/** Length of the right derivations of non-terminal F. */
	private Integer W(int F) {
		return R(F).size() - 1;
	}

	/** Return the current symbol that the stack points on.	*/
	public String getTextSymbol() {
		//if stack is empty, return
		if(T.empty())
			return "";
		
		//get top element of stack
		Command top = T.peek();
		Integer X = top.variable(), i = top.length(), d = top.direction();
		
		//print symbol
		switch (d) {
		case Command.LEFT_DIRECTION:
			return String.valueOf((char) L(X, i).intValue());
		case Command.RIGHT_DIRECTION:
			return String.valueOf((char) R(X, i).intValue());
		default:
			return "";
		}
	}

	/** Returns true if debug mode is set. */
	public boolean debugMode() {
		return DEBUG_MODE;
	}

	/** Returns true if we have reached the left or right border of the text. */
	public boolean atBorder() {
		if(border) {
			border = !border;
			return true;
		} else 
			return false;
	}

	/** Get some informations to display. */
	public String getInfoText() {
		String ret = "<html><body>" +
			"Navigate with left and right arrow keys through the file." +
			"<br />" +
			"<span color=red>"+
			infoText+
			"</span>" +
			"</body></html>";
		
		infoText = "&nbsp;";
		
		return ret;
	}
	
	/** Get debug informations. */
	public String getDebugText() {
		if(DEBUG_MODE) {
			return
				"<html><body>"+
				"Grammar has " +rules.size() + " rules.<br />"+
				"Stack T = " + T.toString() +
				"</body></html>";
		}
		return "";
	}
}
